﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Text.RegularExpressions;
using System.Net;
using HtmlAgilityPack;
using NCrawler;
using NCrawler.Interfaces;
using NCrawler.Services;
using NCrawler.Extensions;
using Polenter.Serialization;

namespace ZCrawler
{
    public enum ZTaskStatus
    {
        Idle,
        Crawling,
        Publishing
    }
    public class ZTask
    {
        public string Url { get; set; }
        public string CSVFile { get; set; }
        public string TaskID { get; set; }
        public ZAnalysis analysis { get; set; }
        [ExcludeFromSerialization]
        public ZTaskStatus status { get; set; }
        public ZAnalysis navAnalyst { get; set; }
        public ZAnalysis pageSwitchAnalyst { get; set; }
        public ZPublisher publisher { get; set; }
        private int CrawlDepth { get; set; }
        public int CrawlNum { get; set; }
        public bool isCSVTask { get; set; }
        public Action<ZTask,ZResult> onStepDataOut;
        public Action<ZTask> willStartTask;
        public Action<ZTask> didTaskCompleted;
        public Action<ZTask> onStatusChanged;
        public Action<string,ZResult> onPublishingStep;
        public Action<string> onEndPublishing;
        public Action<string> onPublishingError;
        private List<Crawler> crawlers;
        public ZResultSet resultSet;
        private bool IsForceStop;
        private int depth;
        static int m_depth = -1;

        public static IFilter[] ExtensionsToSkip = new[]
			{
				(RegexFilter)new Regex(@"(\.jpg|\.css|\.js|\.gif|\.jpeg|\.png|\.ico)",
					RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)
			};
        public ZTask()
        {
            TaskID = DateTime.Now.GetHashCode().ToString();
            status = ZTaskStatus.Idle;
            IsForceStop = false;
            CrawlDepth = 1;
            CrawlNum = 3;
            crawlers = new List<Crawler>();
            resultSet = new ZResultSet();
            isCSVTask = false;
        }
        public void Clean()
        {
            if (pageSwitchAnalyst != null) pageSwitchAnalyst.Clean();
            if (navAnalyst != null) navAnalyst.Clean();
            if (analysis != null) analysis.Clean();
            status = ZTaskStatus.Idle;
            resultSet.Clear();
            IsForceStop = false;
            depth = 0;
        }
        public void NavCrawl(Uri startPage)
        {
            if (!isCSVTask)
            {
                navAnalyst.onSingleProcessDone = new Action<ZResult>((r) =>
                {
                    if (r["nav"] != null)
                    {
                        if (!r["nav"].ToString().IsNullOrEmpty())
                        {
                            Uri uri = new Uri(Url);
                            Uri navigationNode = new Uri(new Uri(uri, r["nav"].ToString()).AbsoluteUri);
                            NormalCrawl(navigationNode);
                        }
                    }
                });
                Crawl(startPage, navAnalyst);
            }
            else
            {
                CSVParser.ParseFields(CSVFile, (d) =>
                    {
                        if (d["nav"] != null)
                        {
                            if (!d["nav"].ToString().IsNullOrEmpty())
                            {
                                Uri navigationNode = new Uri(d["nav"].ToString());
                                NormalCrawl(navigationNode);
                            }
                        }
                    }, "nav");
            }
        }
        public void NormalCrawl(Uri startPage)
        {
            analysis.onSingleProcessDone = new Action<ZResult>((a_r) =>
            {
                if (a_r == null) return;
                StepOut(a_r);
            });
            Crawl(startPage, analysis);
        }
        public void Launch()
        {
            if (status == ZTaskStatus.Crawling || status == ZTaskStatus.Publishing)
                return;
            Clean();
            SetStatus(ZTaskStatus.Crawling);
            if (willStartTask != null) willStartTask(this);
            if (pageSwitchAnalyst != null)
            {
                pageSwitchAnalyst.onSingleProcessDone = new Action<ZResult>((r) =>
                    {
                        if (!r["url"].IsNull() && !r["url"].ToString().IsNullOrEmpty())
                        {
                            try
                            {
                                Uri uri = new Uri(r["url"].ToString());
                                if (navAnalyst != null)
                                {
                                    NavCrawl(uri);
                                }
                                else
                                {
                                    NormalCrawl(uri);
                                }
                                Crawl(uri, pageSwitchAnalyst);
                            }
                            catch (Exception)
                            { }
                        }
                    });
                if (navAnalyst != null)
                {
                    NavCrawl(new Uri(Url));
                }
                else
                {
                    NormalCrawl(new Uri(Url));
                }
                Crawl(new Uri(Url), pageSwitchAnalyst);
            }
            else
            {
                if (navAnalyst != null)
                {
                    if (!isCSVTask) NavCrawl(new Uri(Url));
                    else NavCrawl(null);
                }
                else
                {
                    NormalCrawl(new Uri(Url));
                }
            }
            crawlers.Clear();
            //Publish Process
            Publish();
            SetStatus(ZTaskStatus.Idle);
            if (didTaskCompleted != null) didTaskCompleted(this);
        }
        private void Crawl(Uri pageToCrawl,params ZAnalysis[] analysisCollection)
        {
            if (IsForceStop) return;
            if (depth > m_depth && m_depth != -1) return;
            //Decode Url
            string url = System.Web.HttpUtility.HtmlDecode(pageToCrawl.AbsoluteUri);
            NCrawlerModule.Setup();
            using (Crawler c = new Crawler(new Uri(url), analysisCollection))
            {
                c.MaximumThreadCount = CrawlNum;
                c.MaximumCrawlDepth = CrawlDepth;
                c.CrawlFinished += c_CrawlFinished;
                crawlers.Add(c);
                c.Crawl();
            }
            depth++;
        }
        void c_CrawlFinished(object sender, NCrawler.Events.CrawlFinishedEventArgs e)
        {
            crawlers.Remove(e.Crawler);
        }
        public void Terminate()
        {
            if (status == ZTaskStatus.Idle)
                return;
            IsForceStop = true;
        }
        public void StepOut(ZResult a_r)
        {
            resultSet.Add(a_r);
            if(onStepDataOut != null) onStepDataOut(this,a_r);
        }

        public void Publish()
        {
            if (publisher != null)
            {
                if (!publisher.isActivated) return;
                if (IsForceStop) return;
                SetStatus(ZTaskStatus.Publishing);
                ZPublisherManager.Process
                (
                    resultSet,
                    publisher,
                    (e) =>
                    {
                        if (onPublishingError != null)
                            onPublishingError(e.Message);
                    },
                    () =>
                    {
                        if (onEndPublishing != null)
                            onEndPublishing(TaskID);
                    },
                    (r) =>
                    {
                        if (onPublishingStep != null)
                            onPublishingStep(TaskID, r);
                    }
                );
            }
        }

        private void SetStatus(ZTaskStatus stat)
        {
            status = stat;
            if (onStatusChanged != null) onStatusChanged(this);
        }
    }
}
